/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.android.threadsample; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.net.Uri; import android.util.AttributeSet; import android.view.View; import android.widget.ImageView; import java.lang.ref.WeakReference; import java.net.URL; /** * This class extends the standard Android ImageView View class with some features * that are useful for downloading, decoding, and displaying Picasa images. * */ public class PhotoView extends ImageView { // Indicates if caching should be used private boolean mCacheFlag; // Status flag that indicates if onDraw has completed private boolean mIsDrawn; /* * Creates a weak reference to the ImageView in this object. The weak * reference prevents memory leaks and crashes, because it automatically tracks the "state" of * the variable it backs. If the reference becomes invalid, the weak reference is garbage- * collected. * This technique is important for referring to objects that are part of a component lifecycle. * Using a hard reference may cause memory leaks as the value continues to change; even worse, * it can cause crashes if the underlying component is destroyed. Using a weak reference to * a View ensures that the reference is more transitory in nature. */ private WeakReference<View> mThisView; // Contains the ID of the internal View private int mHideShowResId = -1; // The URL that points to the source of the image for this ImageView private URL mImageURL; // The Thread that will be used to download the image for this ImageView private PhotoTask mDownloadThread; /** * Creates an ImageDownloadView with no settings * @param context A context for the View */ public PhotoView(Context context) { super(context); } /** * Creates an ImageDownloadView and gets attribute values * @param context A Context to use with the View * @param attributeSet The entire set of attributes for the View */ public PhotoView(Context context, AttributeSet attributeSet) { super(context, attributeSet); // Gets attributes associated with the attribute set getAttributes(attributeSet); } /** * Creates an ImageDownloadView, gets attribute values, and applies a default style * @param context A context for the View * @param attributeSet The entire set of attributes for the View * @param defaultStyle The default style to use with the View */ public PhotoView(Context context, AttributeSet attributeSet, int defaultStyle) { super(context, attributeSet, defaultStyle); // Gets attributes associated with the attribute set getAttributes(attributeSet); } /** * Gets the resource ID for the hideShowSibling resource * @param attributeSet The entire set of attributes for the View */ private void getAttributes(AttributeSet attributeSet) { // Gets an array of attributes for the View TypedArray attributes = getContext().obtainStyledAttributes(attributeSet, R.styleable.ImageDownloaderView); // Gets the resource Id of the View to hide or show mHideShowResId = attributes.getResourceId(R.styleable.ImageDownloaderView_hideShowSibling, -1); // Returns the array for re-use attributes.recycle(); } /** * Sets the visibility of the PhotoView * @param visState The visibility state (see View.setVisibility) */ private void showView(int visState) { // If the View contains something if (mThisView != null) { // Gets a local hard reference to the View View localView = mThisView.get(); // If the weak reference actually contains something, set the visibility if (localView != null) localView.setVisibility(visState); } } /** * Sets the image in this ImageView to null, and makes the View visible */ public void clearImage() { setImageDrawable(null); showView(View.VISIBLE); } /** * Returns the URL of the picture associated with this ImageView * @return a URL */ final URL getLocation() { return mImageURL; } /* * This callback is invoked when the system attaches the ImageView to a Window. The callback * is invoked before onDraw(), but may be invoked after onMeasure() */ @Override protected void onAttachedToWindow() { // Always call the supermethod first super.onAttachedToWindow(); // If the sibling View is set and the parent of the ImageView is itself a View if ((this.mHideShowResId != -1) && ((getParent() instanceof View))) { // Gets a handle to the sibling View View localView = ((View) getParent()).findViewById(this.mHideShowResId); // If the sibling View contains something, make it the weak reference for this View if (localView != null) { this.mThisView = new WeakReference<View>(localView); } } } /* * This callback is invoked when the ImageView is removed from a Window. It "unsets" variables * to prevent memory leaks. */ @Override protected void onDetachedFromWindow() { // Clears out the image drawable, turns off the cache, disconnects the view from a URL setImageURL(null, false, null); // Gets the current Drawable, or null if no Drawable is attached Drawable localDrawable = getDrawable(); // if the Drawable is null, unbind it from this VIew if (localDrawable != null) localDrawable.setCallback(null); // If this View still exists, clears the weak reference, then sets the reference to null if (mThisView != null) { mThisView.clear(); mThisView = null; } // Sets the downloader thread to null this.mDownloadThread = null; // Always call the super method last super.onDetachedFromWindow(); } /* * This callback is invoked when the system tells the View to draw itself. If the View isn't * already drawn, and its URL isn't null, it invokes a Thread to download the image. Otherwise, * it simply passes the existing Canvas to the super method */ @Override protected void onDraw(Canvas canvas) { // If the image isn't already drawn, and the URL is set if ((!mIsDrawn) && (mImageURL != null)) { // Starts downloading this View, using the current cache setting mDownloadThread = PhotoManager.startDownload(this, mCacheFlag); // After successfully downloading the image, this marks that it's available. mIsDrawn = true; } // Always call the super method last super.onDraw(canvas); } /** * Sets the current View weak reference to be the incoming View. See the definition of * mThisView * @param view the View to use as the new WeakReference */ public void setHideView(View view) { this.mThisView = new WeakReference<View>(view); } @Override public void setImageBitmap(Bitmap paramBitmap) { super.setImageBitmap(paramBitmap); } @Override public void setImageDrawable(Drawable drawable) { // The visibility of the View int viewState; /* * Sets the View state to visible if the method is called with a null argument (the * image is being cleared). Otherwise, sets the View state to invisible before refreshing * it. */ if (drawable == null) { viewState = View.VISIBLE; } else { viewState = View.INVISIBLE; } // Either hides or shows the View, depending on the view state showView(viewState); // Invokes the supermethod with the provided drawable super.setImageDrawable(drawable); } /* * Displays a drawable in the View */ @Override public void setImageResource(int resId) { super.setImageResource(resId); } /* * Sets the URI for the Image */ @Override public void setImageURI(Uri uri) { super.setImageURI(uri); } /** * Attempts to set the picture URL for this ImageView and then download the picture. * <p> * If the picture URL for this view is already set, and the input URL is not the same as the * stored URL, then the picture has moved and any existing downloads are stopped. * <p> * If the input URL is the same as the stored URL, then nothing needs to be done. * <p> * If the stored URL is null, then this method starts a download and decode of the picture * @param pictureURL An incoming URL for a Picasa picture * @param cacheFlag Whether to use caching when doing downloading and decoding * @param imageDrawable The Drawable to use for this ImageView */ public void setImageURL(URL pictureURL, boolean cacheFlag, Drawable imageDrawable) { // If the picture URL for this ImageView is already set if (mImageURL != null) { // If the stored URL doesn't match the incoming URL, then the picture has changed. if (!mImageURL.equals(pictureURL)) { // Stops any ongoing downloads for this ImageView PhotoManager.removeDownload(mDownloadThread, mImageURL); } else { // The stored URL matches the incoming URL. Returns without doing any work. return; } } // Sets the Drawable for this ImageView setImageDrawable(imageDrawable); // Stores the picture URL for this ImageView mImageURL = pictureURL; // If the draw operation for this ImageVIew has completed, and the picture URL isn't empty if ((mIsDrawn) && (pictureURL != null)) { // Sets the cache flag mCacheFlag = cacheFlag; /* * Starts a download of the picture file. Notice that if caching is on, the picture * file's contents may be taken from the cache. */ mDownloadThread = PhotoManager.startDownload(this, cacheFlag); } } /** * Sets the Drawable for this ImageView * @param drawable A Drawable to use for the ImageView */ public void setStatusDrawable(Drawable drawable) { // If the View is empty, sets a Drawable as its content if (mThisView == null) { setImageDrawable(drawable); } } /** * Sets the content of this ImageView to be a Drawable resource * @param resId */ public void setStatusResource(int resId) { // If the View is empty, provides it with a Drawable resource as its content if (mThisView == null) { setImageResource(resId); } } }